const fs = require('fs')
const _ = require('lodash')
const _fp = require('lodash/fp')


// 高阶函数--函数作为参数 forEach
function forEach (array, fn){
    for(let i = 0;i < array.length; i++){
       fn(array[i]) 
    }
}

//测试
// let arr = [1, 3, 4, 9, 10]
// forEach(arr, function(item){
//     console.log(item)
// })

// filter
function filter(array, fn){
    let results = []
    for(let i=0;i<array.length;i++){
        if(fn(array[i])){
            results.push(array[i])
        }
    }
    return results
}
//测试
// let arr = [1, 3, 4, 9, 10]
// let r = filter(arr, function(item){
//     return item % 2 === 0
// })
// console.log(r, 'r')



// 高阶函数--函数作为返回值 ;  另 闭包案例
function makeFn(){
    let msg = 'Hello'
    return function(){
        console.log(msg)
    }
}
// const fn = makeFn()
// fn()

// makeFn()()


// once 函数 lodash ;  另 闭包案例
function once(fn){
    let done = false 
    return function(){
        if(!done){
            done = true
            return fn.apply(this, arguments)
        }
    }
}

let pay = once(function(money){
    console.log('支付: ' + money)
})

// pay(4)
// pay(4)
// pay(4)
// pay(4)
// pay(4)
// pay(4)
// pay(4)


// ---------------------------------------------------
// 常用高阶函数 map every some

// map
// const map = (array, fn) => {
//     let results = []
//     for(let value of array){
//         results.push(fn(value))
//     }
//     return results
// }
// let arr = [1,3, 4, 5]
// arr = map(arr, item => item * item)
// console.log(arr)


//every
const every = (array, fn) => {
    let result = true 
    for(let value of array){
        result = fn(value)
        if(!result){
            break
        }
    }
    return result
}

// let arr = [1, 3, 5, 5,6, 20]
// let r = every(arr, v => v > 10)
// console.log(r)




//some
const some = (array, fn) => {
    let result = false 
    for(let value of array){
        result = fn(value)
        if(result){
            break
        }
    }
    return result
}

// let arr = [1, 3, 5, 7]
// let r = some(arr, v => v % 2 === 0)
// console.log(r)


// ----------------------------------------------
// 纯函数 lodash是一个纯函数的功能库(数组、数字、对象、字符串、函数等的操作方法)

// slice   splice
let array = [1, 3, 5,6,4]
// console.log(array.slice(0, 3))
// console.log(array.slice(0, 3))
// console.log(array.slice(0, 3))
// slice 纯函数


// splice 不纯函数
// console.log(array.splice(0, 3))
// console.log(array.splice(0, 3))
// console.log(array.splice(0, 3))

// 求和纯函数
function getSum (n1, n2){
    return n1 + n2
}
// console.log(getSum(2, 4))
// console.log(getSum(2, 4))
// console.log(getSum(2, 4))



// -----------------------------------
// 纯函数的好处
// 1. 可缓存(记忆函数)


function getArea (r){
    console.log(r, 'r')
    return Math.PI * r * r
}

// let getAreaWithMemery = _.memoize(getArea)
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))

// 模拟 memoize方法实现
function memoize (fn){
    let cache = {}
    return function(){
        console.log(arguments[0], fn)
        let key = JSON.stringify(arguments)
        console.log(key, fn.apply(fn, arguments), 'key')
        cache[key] = cache[key] || fn.apply(fn, arguments)
        return cache[key]
    }
}
// let getAreaWithMemery = memoize(getArea)
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))
// console.log(getAreaWithMemery(4))



// ------------------------------------------------
//  柯里化演示
// function checkAge(age){
//     let min = 18 
//     return age >= min
// }


// 普通的纯函数
// function checkAge(min, age){
//     return age >= min
// }


// console.log(checkAge(18, 20))
// console.log(checkAge(18, 20))
// console.log(checkAge(22, 20))
// console.log(checkAge(24, 20))

// 函数柯里化
// function checkAge(min){
//     return function(age){
//         return age >= min
//     }
// }
// ES6
let checkAge = min => (age => age >= min)

// let checkAge18 = checkAge(18)
// console.log(checkAge18(20))
// console.log(checkAge18(22))
// console.log(checkAge18(16))



// lodash中的柯里化

function getSum(a, b, c){
    return a + b + c
}
// const curried = _.curry(getSum)
// console.log(curried(2, 3, 5))
// console.log(curried(2)(3, 5))
// console.log(curried(2)(3)(2))
// console.log(curried(2, 3)(2))

// 柯里化案例
// ''.match(/\s+/g)
// ''.match(/\d+/g)
// function match (reg, str){
//     return str.match(reg)
// }

const match = _.curry(function (reg, str){
    return str.match(reg)
})

const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
// console.log(haveSpace('hello'))
// console.log(haveNumber('123456asldk'))

const filter1 = _.curry(function(fn, array){
    return array.filter(fn)
})

// console.log(filter1(['ldkk llll', 'liuchao'], haveSpace))
const findSpace = filter1(haveSpace)
// console.log(findSpace(['ldkk llll', 'liuchao']))



// ------------------------------------------------
// 柯里化实现原理  (模拟实现lodash.curry方法)
// function curry(fn){
//     return function curriedFn(...args){
//        //判断实参和行参的个数
//        if(args.length < fn.length){
//            return function(){
//             //    return curriedFn(...args.concat([...arguments]))
//                return curriedFn(...args.concat(Array.from(arguments)))
//            }
//        } 
//        return fn(...args)
//     }
// }

// let curried1 = curry(getSum)

// console.log(curried1(1, 2, 3))
// console.log(curried1(1)(2)(3))
// console.log(curried1(1)(2, 3))
// console.log(curried1(1, 2)(3))


// ----------------------------------
// 函数组合
// function compose(f, g){
//     return function(value){
//         return f(g(value))
//     }
// }

// function reverse (array){
//     return array.reverse()
// }

// function first (array){
//     return array[0]
// }

// const last = compose(first, reverse)
// console.log(last([1, 3, 5, 2]))


// ------------------------------------------------
// lodash中的组合函数 flow (从左向右执行) flowRight (从右向左执行)
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()

// const f = _.flowRight(toUpper, first, reverse)
// const f = compose(toUpper, first, reverse)
// console.log(f(['aldj', 'kdkdkd']))

// 组合函数的实现原理 模拟lodash.flowRight
// function compose (...args){
//     return function(value){
//         return args.reverse().reduce((acc, fn) => {
//             return fn(acc)
//         }, value)
//     }
// }

// 箭头函数实现
// const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)
// const f = compose(toUpper, first, reverse)
// console.log(f(['aldj', 'kdkdkd']))

// 结合律(函数组合的特点) 
// const h = _.flowRight(_.toUpper, _.first, _.reverse)
// const g = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))
// const h = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
// console.log(h(['aldj', 'kdkdkd']))
// console.log(g(['aldj', 'kdkdkd']))


// 组合函数如何调试
// 实现: NEVER SAY DIE -> never-say-die

// const split = _.curry((sep, str) => _.split(str, sep))

// const join = _.curry((sep, array) => _.join(array, sep))

// const log = v => {
//     console.log(v)
//     return v
// }
// const trace = _.curry((tag, v) => {
//     console.log(tag, v)
//     return v
// })
// const map = _.curry((fn, array) => _.map(array, fn))
// // const f = _.flowRight(join('-'), log, map(_.toLower), log, split(' '))
// const f = _.flowRight(join('-'), trace('map 后'), map(_.toLower), trace('map 前'), split(' '))

// console.log(f('NEVER SAY DIE'))








// --------------------------------------------
// lodash中的FP模块
// const fp_f = _fp.flowRight(_fp.join('-'), _fp.map(_fp.toLower), _fp.split(' '))

// console.log(fp_f('NEVER SAY DIE'))


// lodash中的FP模块  和非FP模块 map方法的区别
// console.log(_.map(['23', '3', '10'], parseInt))

// parseInt('23', 0, array)
// parseInt('8', 1, array)
// parseInt('10', 2, array)

// console.log(_fp.map(parseInt, ['23', '3', '10']))




// ----------------------------------------
// PrintFree 模式
// Hello    World -> hello_world
// const f = _fp.flowRight(_fp.replace(/\s+/g, '_'), _fp.toLower)
// console.log(f('Hello    World'))

// PrintFree 案例
// world wild web -> W. W. W.
// const firstLetterToUpper = _fp.flowRight(_fp.join('. '), _fp.map(_fp.first), _fp.map(_fp.toUpper), _fp.split(' '))
// const firstLetterToUpper = _fp.flowRight(_fp.join('. '), _fp.map(_fp.flowRight(_fp.first, _fp.toUpper)), _fp.split(' '))
// console.log(firstLetterToUpper('world wild web'))


// ----------------------------------------------------
// 函子 Functor
// class Container {
//     constructor (value){
//         this._value = value 
//     }
//     map(fn){
//         return new Container(fn(this._value))
//     }
// }


// let r = new Container(5).map(x => x + 1).map(x => x * x)
// console.log(r)

class Container {
    static of (value){
        return new Container(value)
    }
    constructor (value){
        this._value = value 
    }
    map(fn){
        return new Container(fn(this._value))
    }
}

// let r = Container.of(5).map(x => x + 1).map(x => x * x)
// console.log(r)

// 问题 (传入值是null undefined)
// Container.of(null).map(x => x.toUpperCase())


// MayBe函子
class MayBe {
    static of(value){
        return new MayBe(value)
    }
    constructor(value){
        this._value = value
    }
    map(fn){
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }

    isNothing(){
        return this._value === null || this._value === undefined
    }
}

// let r = MayBe.of('Hello kitty').map(x => x.toUpperCase())
// console.log(r)
// let r = MayBe.of(null).map(x => x.toUpperCase())
// console.log(r)

// let r = MayBe.of(null).map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '))
// console.log(r)


// Either函子
class Left {
    static of(value){
        return new Left(value)
    }
    constructor(value){
        this._value = value
    }
    map(fn){
        return this
    }
}

class Right {
    static of(value){
        return new Right(value)
    }
    constructor(value){
        this._value = value
    }
    map(fn){
        return Right.of(fn(this._value))
    }
}

// let r1 = Right.of(12).map(x => x + 2)
// let r2 = Left.of(12).map(x => x + 2)
// console.log(r1)
// console.log(r2)

function parseJSON (str) {
    try{
        return Right.of(JSON.parse(str))
    } catch(e){
        return Left.of({error: e.message})
    }
}

// let r = parseJSON('{name: zs}')
// console.log(r)
// let r = parseJSON('{"name": "zs"}').map(x => x.name.toUpperCase())
// console.log(r)


// IO函子
// class IO {
//     static of(value){
//         return new IO(function(){
//             return value 
//         })
//     }
//     constructor(fn){
//         this._value = fn
//     }
//     map(fn){
//         return new IO(_fp.flowRight(fn, this._value))
//     }
// }

// let r = IO.of(process).map(v => v.execPath)
// console.log(r)
// console.log(r._value())

// IO函子 Monad函子
class IO {
    static of(value){
        return new IO(function(){
            return value 
        })
    }
    constructor(fn){
        this._value = fn
    }
    map(fn){
        return new IO(_fp.flowRight(fn, this._value))
    }
    join(){
        return this._value()
    }
    flatMap(fn){
        return this.map(fn).join()
    }
}

// IO函子的问题
let readFile = function(filename){
    return new IO(function(){
        return fs.readFileSync(filename, 'utf-8')
    })
}
let print = function(x){
    return new IO(function(){
        console.log(x)
        return x
    })
}
// let cat = _fp.flowRight(print, readFile)
//IO(IO(x))
// let r = cat('package.json')
// console.log(r)
// let r = cat('package.json')._value()._value()
// console.log(r)


let r = readFile('package.json')
// .map(x => x.toUpperCase())
.map(_fp.toUpper)
.flatMap(print).join()
console.log(r)









// folktale库(2.3.2)
const { compose, curry } = require('folktale/core/lambda')

// let f = curry(2, (x, y) => {
//     return x + y 
// })
// console.log(f(1, 3))
// console.log(f(1)(3))

// let f = compose(_fp.toUpper, _fp.first)
// console.log(f(['one', 'tow']))



// Task函子 处理异步任务

// const { task } = require('folktale/concurrency/task') 

// function readFile (filename){
//     return task( resolver => {
//         fs.readFile(filename, 'utf-8', (err, data) => {
//             if(err) resolver.reject(err)
//             resolver.resolve(data)
//         })
//     })
// }

// let r = readFile('package.json').map(_fp.split('\n')).map(_fp.find(x => x.includes('version'))).run().listen({
//     onRejected: err => {
//        console.log(err) 
//     },
//     onResolved: value => {
//         console.log(value)
//     }
// })








